package com.one.blocks.rbac.config;

import com.alicp.jetcache.anno.support.SpringConfigProvider;
import com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.one.blocks.rbac.filter.BizExceptionHandlerFilter;
import com.one.blocks.rbac.jetcache.JacksonKeyConvertor;
import com.one.blocks.rbac.jetcache.JacksonValueDecoder;
import com.one.blocks.rbac.jetcache.JacksonValueEncoder;
import com.one.blocks.rbac.jetcache.jackson.LocalDateTimeFromLong;
import com.one.blocks.rbac.jetcache.jackson.LocalDateTimeToLong;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Function;

/**
 * @author linfuxin Created on 2022-02-14 14:12:16
 */
@MapperScan("com.one.blocks.rbac.mapper")
@ComponentScan(value = {
        "com.one.blocks.rbac.config", "com.one.blocks.rbac.controller",
        "com.one.blocks.rbac.manager", "com.one.blocks.rbac.security"
})
@AutoConfigureBefore(JetCacheAutoConfiguration.class)
@PropertySource("classpath:application-rbac.properties")
public class RbacAutoConfig {

    @Bean
    @ConditionalOnMissingBean(SpringConfigProvider.class)
    public SpringConfigProvider springConfigProvider() {
        String myConvertor = "jackson";
        ObjectMapper objectMapper = new ObjectMapper();
        // 指定序列化输入类型,序列化时带上@class等字段，避免复杂对象反序列化为LinkedHashMap
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        final SimpleModule module = new SimpleModule();
        module.addSerializer(LocalDateTime.class, new LocalDateTimeToLong());
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeFromLong());
        objectMapper.registerModule(module);

        return new SpringConfigProvider() {

            @Override
            public Function<Object, Object> parseKeyConvertor(String convertor) {
                if (myConvertor.equalsIgnoreCase(convertor)) {
                    return new JacksonKeyConvertor(objectMapper);
                } else {
                    return super.parseKeyConvertor(convertor);
                }
            }

            @Override
            public Function<Object, byte[]> parseValueEncoder(String valueEncoder) {
                if (myConvertor.equalsIgnoreCase(valueEncoder)) {
                    return new JacksonValueEncoder(objectMapper);
                } else {
                    return super.parseValueEncoder(valueEncoder);
                }
            }

            @Override
            public Function<byte[], Object> parseValueDecoder(String valueDecoder) {
                if (myConvertor.equalsIgnoreCase(valueDecoder)) {
                    return new JacksonValueDecoder(objectMapper);
                } else {
                    return super.parseValueDecoder(valueDecoder);
                }
            }
        };
    }

    @Bean
    @ConditionalOnProperty(prefix = "app.blocks.security.authc.login", name = "enable", havingValue = "true", matchIfMissing = true)
    public PasswordEncoder passwordEncoder() {
        DelegatingPasswordEncoder passwordEncoder = (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();
        passwordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder(4));
        return passwordEncoder;
    }

    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String pattern;

    @Bean
    @ConditionalOnMissingBean(Jackson2ObjectMapperBuilderCustomizer.class)
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> {
            jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
            jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
            jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
        };
    }

    @Value("${app.error.biz-path:${error.biz-path:/biz/error}}")
    private String errorPath;

    @Bean
    @ConditionalOnMissingBean(BizExceptionHandlerFilter.class)
    public FilterRegistrationBean<BizExceptionHandlerFilter> bizExceptionHandlerFilter() {
        FilterRegistrationBean<BizExceptionHandlerFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new BizExceptionHandlerFilter(errorPath));
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(-2);
        return registrationBean;
    }
}